/*
 * SPDX-FileCopyrightText: 2023-2025 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test.output

import android.content.Context
import android.icu.lang.UCharacter
import android.icu.lang.UProperty
import android.util.Log
import com.sanji.audio_test.Preferences
import com.sanji.audio_test.template.Template
import java.text.ParsePosition
import java.time.DateTimeException
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.format.DateTimeParseException
import java.time.format.SignStyle
import java.time.temporal.ChronoField
import java.time.temporal.Temporal

/**
 * 输出路径数据类
 * 包含路径值和脱敏后的字符串表示
 */
data class OutputPath(
    val value: List<String>,     // 路径值列表
    val redacted: String,        // 脱敏后的字符串表示
) {
    // 未脱敏的完整路径字符串
    val unredacted = value.joinToString("/")

    // 重写toString方法，返回脱敏后的字符串
    override fun toString() = redacted
}

/**
 * 输出文件名生成器辅助类
 * 根据通话信息确定录音输出文件名
 */
class OutputFilenameGenerator(
    private val context: Context,  // 应用上下文
) {
    // 模板相关
    private val filenameTemplate = Preferences(context).filenameTemplate
        ?: Preferences.DEFAULT_FILENAME_TEMPLATE  // 文件名模板
    private val dateVarLocations = filenameTemplate.findVariableRef(DATE_VAR)?.second  // 日期变量位置

    // 时间戳相关
    private var formatter = FORMATTER  // 日期时间格式化器

    // 脱敏相关
    private val redactions = HashMap<String, String>()  // 脱敏映射表
    private val redactionsSorted = mutableListOf<Pair<String, String>>()  // 排序后的脱敏列表
    val redactor = object : OutputDirUtils.Redactor {
        override fun redact(msg: String): String {
            synchronized(this@OutputFilenameGenerator) {
                var result = msg

                // 按长度降序替换，提高脱敏准确性
                for ((source, target) in redactionsSorted) {
                    result = result.replace(source, target)
                }

                return result
            }
        }
    }

    /**
     * 初始化块，记录文件名模板信息
     */
    init {
        Log.i(TAG, "文件名模板: $filenameTemplate")
    }

    /**
     * 添加脱敏规则
     *
     * @param source 原始字符串
     * @param target 替换字符串
     */
    private fun addRedaction(source: String, target: String) {
        synchronized(this) {
            redactions[source] = target

            // 基于关键字的脱敏对于任意文件名永远不可能100%可靠，但我们可以通过先替换最长的字符串来提高成功率
            redactionsSorted.clear()
            redactions.entries
                .mapTo(redactionsSorted) { it.key to it.value }
                .sortByDescending { it.first.length }
        }
    }

    /**
     * 格式化电话号码
     *
     * @param number 电话号码对象
     * @param arg 格式参数
     * @return 格式化后的电话号码字符串
     */
    private fun formatPhoneNumber(number: PhoneNumber, arg: String?): String? {
        return when (arg) {
            // 默认已经是E.164格式
            null, "E.164" -> number.toString()
            "digits_only" -> number.format(context, PhoneNumber.Format.DIGITS_ONLY)
            "formatted" -> number.format(context, PhoneNumber.Format.COUNTRY_SPECIFIC)
                // 不要失败，因为这不是用户的错
                ?: number.toString()
            else -> {
                Log.w(TAG, "未知的phone_number格式参数: $arg")
                null
            }
        }
    }

    /**
     * 评估变量值
     *
     * @param name 变量名
     * @param arg 变量参数
     * @param metadata 通话元数据
     * @return 变量对应的值
     */
    private fun evaluateVars(
        name: String,
        arg: String?,
        metadata: CallMetadata,
    ): String? {
        when (name) {
            "date" -> {
                // 如果有参数，则使用自定义日期时间模式
                if (arg != null) {
                    Log.d(TAG, "使用自定义日期时间模式: $arg")

                    synchronized(this) {
                        try {
                            formatter = DateTimeFormatterBuilder()
                                .appendPattern(arg)
                                .toFormatter()
                        } catch (e: Exception) {
                            Log.w(TAG, "无效的自定义日期时间模式: $arg; 使用默认值", e)
                        }
                    }
                }

                return formatter.format(metadata.timestamp)
            }
            "direction" -> return metadata.direction?.toString()
            "sim_slot" -> {
                // 仅当设备有多个活动SIM卡时才追加SIM卡槽ID
                if (arg == "always" || (metadata.simCount != null && metadata.simCount > 1)) {
                    return metadata.simSlot?.toString()
                }
            }
            "phone_number" -> {
                // 获取所有参与方的电话号码并连接
                val joined = metadata.calls.asSequence()
                    .map { it.phoneNumber?.let { number -> formatPhoneNumber(number, arg) } }
                    .filterNotNull()
                    .joinToString(",")
                if (joined.isNotEmpty()) {
                    addRedaction(joined, "<电话号码>")
                    return joined
                }
            }
            "caller_name" -> {
                // 获取所有参与方的来电者名称并连接
                val joined = metadata.calls.asSequence()
                    .map { it.callerName?.trim() }
                    .filter { n -> !n.isNullOrEmpty() }
                    .joinToString(",")
                if (joined.isNotEmpty()) {
                    addRedaction(joined, "<来电者名称>")
                    return joined
                }
            }
            "contact_name" -> {
                // 获取所有参与方的联系人名称并连接
                val joined = metadata.calls.asSequence()
                    .map { it.contactName?.trim() }
                    .filter { n -> !n.isNullOrEmpty() }
                    .joinToString(",")
                if (joined.isNotEmpty()) {
                    addRedaction(joined, "<联系人名称>")
                    return joined
                }
            }
            "call_log_name" -> {
                // 获取通话记录名称
                val cachedName = metadata.callLogName?.trim()
                if (!cachedName.isNullOrEmpty()) {
                    addRedaction(cachedName, "<通话记录名称>")
                    return cachedName
                }
            }
            else -> {
                Log.w(TAG, "未知的文件名模板变量: $name")
            }
        }

        return null
    }

    /**
     * 根据模板生成输出路径
     *
     * @param template 模板对象
     * @param metadata 通话元数据
     * @return 输出路径对象
     */
    private fun generate(template: Template, metadata: CallMetadata): OutputPath {
        // 评估模板并生成路径字符串
        val newPathString = template.evaluate { name, arg ->
            val result = evaluateVars(name, arg, metadata)?.trim()

            // 模板中允许目录，但变量值中不允许，除非是时间戳的一部分（因为那是用户完全控制的）
            when (name) {
                DATE_VAR -> result
                else -> result?.replace('/', '_')
            }
        }
        // 分割路径并清理各部分
        val newPath = splitPath(newPathString).map(::sanitizePathComponent)

        return OutputPath(newPath, redactor.redact(newPath))
    }

    /**
     * 生成输出路径
     *
     * @param metadata 通话元数据
     * @return 输出路径对象
     */
    fun generate(metadata: CallMetadata): OutputPath {
        val path = try {
            generate(filenameTemplate, metadata)
        } catch (e: Exception) {
            // 如果自定义模板失败，则使用默认模板
            if (filenameTemplate === Preferences.DEFAULT_FILENAME_TEMPLATE) {
                throw e
            } else {
                Log.w(TAG, "评估自定义模板失败: $filenameTemplate", e)
                generate(Preferences.DEFAULT_FILENAME_TEMPLATE, metadata)
            }
        }

        Log.i(TAG, "生成的文件名: $path")

        return path
    }

    /**
     * 从输入字符串的指定位置解析时间戳
     *
     * @param input 输入字符串
     * @param startPos 起始位置
     * @return 解析出的时间对象
     */
    private fun parseTimestamp(input: String, startPos: Int): Temporal? {
        val pos = ParsePosition(startPos)
        val parsed = synchronized(this) {
            formatter.parse(input, pos)
        }

        return try {
            parsed.query(ZonedDateTime::from)
        } catch (_: DateTimeException) {
            try {
                // 自定义模式可能未指定时区
                parsed.query(LocalDateTime::from)
            } catch (_: DateTimeException) {
                // 自定义模式可能只指定日期没有时间
                parsed.query(LocalDate::from).atStartOfDay()
            }
        }
    }

    /**
     * 从输入字符串解析时间戳
     *
     * @param input 输入字符串
     * @return 解析出的时间对象
     */
    private fun parseTimestamp(input: String): Temporal? {
        if (dateVarLocations != null) {
            for (location in dateVarLocations) {
                when (location) {
                    is Template.VariableRefLocation.AfterPrefix -> {
                        var searchIndex = 0

                        while (true) {
                            val literalPos = input.indexOf(location.literal, searchIndex)
                            if (literalPos < 0) {
                                break
                            }

                            val timestampPos = literalPos + location.literal.length

                            try {
                                return parseTimestamp(input, timestampPos)
                            } catch (_: DateTimeParseException) {
                                // 忽略解析异常
                            } catch (e: DateTimeException) {
                                Log.w(TAG, "意外的非DateTimeParseException错误", e)
                            }

                            if (location.atStart) {
                                break
                            } else {
                                searchIndex = timestampPos
                            }
                        }
                    }
                    Template.VariableRefLocation.Arbitrary -> {
                        Log.d(TAG, "日期可能在任意位置")
                    }
                }
            }
        }

        return null
    }

    /**
     * 从路径解析时间戳
     *
     * @param path 路径列表
     * @return 解析出的时间对象
     */
    fun parseTimestampFromPath(path: List<String>): Temporal? {
        val pathString = path.joinToString("/")
        val redacted = redactTruncate(pathString)
        val timestamp = parseTimestamp(pathString)

        Log.d(TAG, "从 $redacted 解析出 $timestamp")

        return timestamp
    }

    /**
     * 验证错误类型枚举
     */
    enum class ValidationErrorType {
        UNKNOWN_VARIABLE,    // 未知变量
        HAS_ARGUMENT,        // 不应有参数
        INVALID_ARGUMENT,    // 无效参数
    }

    /**
     * 验证错误数据类
     */
    data class ValidationError(
        val type: ValidationErrorType,      // 错误类型
        val varRef: Template.VariableRef,   // 变量引用
    )

    companion object {
        private val TAG = OutputFilenameGenerator::class.java.simpleName

        const val DATE_VAR = "date"  // 日期变量名

        /**
         * 检查代码点是否有效
         *
         * @param codePoint 代码点
         * @return 是否有效
         */
        private fun isValidCodePoint(codePoint: Int): Boolean {
            // 控制字符无效
            if (codePoint >= 0x00 && codePoint <= 0x1f) {
                return false
            }

            // 特殊字符无效
            return when (codePoint) {
                '"'.code,
                '*'.code,
                '/'.code,
                ':'.code,
                '<'.code,
                '>'.code,
                '?'.code,
                '\\'.code,
                '|'.code,
                0x7F -> false
                else -> !UCharacter.hasBinaryProperty(
                    codePoint,
                    UProperty.DEFAULT_IGNORABLE_CODE_POINT,
                )
            }
        }

        /**
         * 清理路径组件，避免Android MediaProvider不允许的代码点
         *
         * AOSP只阻止FAT32中无效的代码点。GrapheneOS额外阻止可忽略的代码点。我们为了安全起见都阻止。
         */
        private fun sanitizePathComponent(name: String) = buildString {
            var i = 0

            while (i < name.length) {
                val codePoint = name.codePointAt(i)

                if (isValidCodePoint(codePoint)) {
                    append(Character.toChars(codePoint))
                } else {
                    append('_')
                }

                i += Character.charCount(codePoint)
            }
        }

        /**
         * 支持的变量列表
         *
         * 保持与[Preferences.DEFAULT_FILENAME_TEMPLATE]中相同的顺序
         */
        val KNOWN_VARS = arrayOf(
            DATE_VAR,
            "direction",
            "sim_slot",
            "phone_number",
            "contact_name",
            "caller_name",
            "call_log_name",
        )

        // 例如: 20220429_180249.123-0400
        private val FORMATTER = DateTimeFormatterBuilder()
            .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
            .appendValue(ChronoField.MONTH_OF_YEAR, 2)
            .appendValue(ChronoField.DAY_OF_MONTH, 2)
            .appendLiteral('_')
            .appendValue(ChronoField.HOUR_OF_DAY, 2)
            .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
            .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
            .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
            .appendOffset("+HHMMss", "+0000")
            .toFormatter()

        /**
         * 分割路径字符串
         *
         * @param pathString 路径字符串
         * @return 分割后的路径列表
         */
        fun splitPath(pathString: String) = pathString
            .splitToSequence('/')
            .filter { it.isNotEmpty() && it != "." && it != ".." }
            .toList()

        /**
         * 截断并脱敏消息
         *
         * @param msg 消息字符串
         * @return 脱敏后的字符串
         */
        fun redactTruncate(msg: String): String = buildString {
            val n = 2

            if (msg.length > 2 * n) {
                append(msg.substring(0, n))
            }
            append("<...>")
            if (msg.length > 2 * n) {
                append(msg.substring(msg.length - n))
            }
        }

        /**
         * 验证模板
         *
         * @param template 模板对象
         * @return 验证错误列表
         */
        fun validate(template: Template): List<ValidationError> {
            val errors = mutableListOf<ValidationError>()

            for (varRef in template.findAllVariableRefs()) {
                when (varRef.name) {
                    "date" -> {
                        if (varRef.arg != null) {
                            try {
                                DateTimeFormatterBuilder()
                                    .appendPattern(varRef.arg)
                                    .toFormatter()
                            } catch (_: Exception) {
                                errors.add(ValidationError(
                                    ValidationErrorType.INVALID_ARGUMENT, varRef))
                            }
                        }
                    }
                    "phone_number" -> {
                        if (varRef.arg !in arrayOf(null, "E.164", "digits_only", "formatted")) {
                            errors.add(ValidationError(
                                ValidationErrorType.INVALID_ARGUMENT, varRef))
                        }
                    }
                    "sim_slot" -> {
                        if (varRef.arg !in arrayOf(null, "always")) {
                            errors.add(ValidationError(
                                ValidationErrorType.INVALID_ARGUMENT, varRef))
                        }
                    }
                    "direction", "caller_name", "contact_name", "call_log_name" -> {
                        if (varRef.arg != null) {
                            errors.add(ValidationError(
                                ValidationErrorType.HAS_ARGUMENT, varRef))
                        }
                    }
                    else -> errors.add(ValidationError(
                        ValidationErrorType.UNKNOWN_VARIABLE, varRef))
                }
            }

            return errors
        }
    }
}